package com.kmxd.ams.app.use;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.kmxd.ams.client.system.ISysDeptSvc;
import com.kmxd.ams.client.system.ISysUserSvc;
import com.kmxd.ams.client.use.IUseAppraiseSvc;
import com.kmxd.ams.client.use.IUseOrderSvc;
import com.kmxd.ams.client.use.IUsePushSvc;
import com.kmxd.ams.client.use.dto.UseAppraiseAddDTO;
import com.kmxd.ams.client.use.dto.UseAppraiseEditDTO;
import com.kmxd.ams.client.use.dto.UseAppraiseQueryDTO;
import com.kmxd.ams.client.use.vo.*;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.StatisticsTypeEnum;
import com.kmxd.ams.core.common.enums.UseOrderTypeEnum;
import com.kmxd.ams.core.common.query.QueryGen;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.util.IdUtil;
import com.kmxd.ams.core.util.SecurityUtil;
import com.kmxd.ams.infra.system.entity.SysDept;
import com.kmxd.ams.infra.system.entity.SysUser;
import com.kmxd.ams.infra.use.entity.UseAppraise;
import com.kmxd.ams.infra.use.mapper.UseAppraiseMapper;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.AllArgsConstructor;
import org.apache.commons.compress.utils.Lists;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 利用评价表 服务实现类
 *
 * @author nickbi
 * @since 2023-01-19
 */
@Service
@AllArgsConstructor
public class UseAppraiseSvcImpl extends ServiceImpl<UseAppraiseMapper, UseAppraise>
    implements IUseAppraiseSvc {

  private final ISysDeptSvc deptSvc;
  private final ISysUserSvc userSvc;
  private UseAppraiseMapper useAppraiseMapper;

  @Override
  public Page<UseAppraiseListVO> selectPage(Page reqPage, UseAppraiseQueryDTO req) {
    LambdaQueryWrapper<UseAppraise> queryWrapper =
        QueryGen.init(new QueryWrapper<UseAppraise>(), req).lambda();
    Page<UseAppraise> page = useAppraiseMapper.selectPage(reqPage, queryWrapper);
    Page<UseAppraiseListVO> voPage = BeanUtil.copyProperties(page, Page.class);
    voPage.setRecords(BeanUtil.copyToList(page.getRecords(), UseAppraiseListVO.class));
    return voPage;
  }

  @Override
  public List<UseAppraiseListVO> selectList(UseAppraiseQueryDTO req) {
    LambdaQueryWrapper<UseAppraise> queryWrapper =
        QueryGen.init(new QueryWrapper<UseAppraise>(), req).lambda();
    List<UseAppraise> list = useAppraiseMapper.selectList(queryWrapper);
    return BeanUtil.copyToList(list, UseAppraiseListVO.class);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void add(UseAppraiseAddDTO req) {
    UseAppraise entity = BeanUtil.copyProperties(req, UseAppraise.class);
    entity.setComId(SecurityUtil.getCropId());
    entity.setFondsId(SecurityUtil.getFondsId());
    entity.setAppraiseUserId(SecurityUtil.getUserId());
    entity.setAppraiseName(SecurityUtil.getLoginUser().getRealName());
    entity.setAppraiseDeptId(SecurityUtil.getLoginUser().getDeptId());
    entity.setYear(DateUtil.year(new Date()));
    // 被评价信息
    if (ImmutableSet.of(1, 2, 3, 4).contains(req.getWorkType())) {
      IUseOrderSvc useOrderSvc = SpringUtil.getBean(IUseOrderSvc.class);
      UseOrderVO useOrderVO = useOrderSvc.view(req.getWorkId());
      entity.setOfferDeptId(useOrderVO.getOpDeptId());
      entity.setOfferFondsId(useOrderVO.getOpFondsId());
      entity.setOfferName(useOrderVO.getOpUsername());
      entity.setOfferUserId(useOrderVO.getOpUserId());
    } else {
      IUsePushSvc usePushSvc = SpringUtil.getBean(IUsePushSvc.class);
      UsePushVO usePushVO = usePushSvc.view(req.getWorkId());
      entity.setOfferDeptId(usePushVO.getPushDeptId());
      entity.setOfferFondsId(usePushVO.getPushFondsId());
      entity.setOfferName(usePushVO.getPushUsername());
      entity.setOfferUserId(usePushVO.getPushUserId());
    }

    useAppraiseMapper.insert(entity);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(UseAppraiseEditDTO req) {
    UseAppraise entity = BeanUtil.copyProperties(req, UseAppraise.class);
    useAppraiseMapper.updateById(entity);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(String ids) {
    Set<Long> idList = IdUtil.split(ids);
    this.baseMapper.deleteBatchIds(idList);
  }

  @Override
  public UseAppraiseVO view(Long id, Long workId) {
    if (ObjectUtil.isAllEmpty(id, workId)) {
      throw BizTipException.instance(ErrorCode.VALIDATE_NOT_NULL, "参数不能为空");
    }
    UseAppraise entity =
        useAppraiseMapper.selectOne(
            Wrappers.<UseAppraise>lambdaQuery()
                .eq(ObjectUtil.isNotNull(id), UseAppraise::getId, id)
                .eq(ObjectUtil.isNotNull(workId), UseAppraise::getWorkId, workId));
    return BeanUtil.copyProperties(entity, UseAppraiseVO.class);
  }

  @Override
  public Page<UseAppraiseScoreListVO> selectScorePage(Page reqPage, UseAppraiseQueryDTO req) {
    QueryWrapper<UseAppraise> wrapper = buildScoreQuery(req);

    Page<UseAppraise> page = useAppraiseMapper.selectPage(reqPage, wrapper);
    Page<UseAppraiseScoreListVO> voPage = new Page<>(reqPage.getCurrent(), reqPage.getSize());
    voPage.setTotal(page.getTotal());
    if (ObjectUtil.isEmpty(page.getRecords())) {
      return voPage;
    }
    List<UseAppraiseScoreListVO> resList = buildScoreList(req.getStatType(), page.getRecords());
    voPage.setRecords(resList);
    return voPage;
  }

  @Override
  public List<UseAppraiseScoreListVO> selectScoreList(UseAppraiseQueryDTO req) {
    QueryWrapper<UseAppraise> wrapper = buildScoreQuery(req);
    List<UseAppraise> list = list(wrapper);
    return buildScoreList(req.getStatType(), list);
  }

  public QueryWrapper<UseAppraise> buildScoreQuery(UseAppraiseQueryDTO req) {
    boolean groupByYear =
        ObjectUtil.isNull(req.getStatType())
            || ObjectUtil.equals(StatisticsTypeEnum.YEAR.getCode(), req.getStatType());
    QueryWrapper<UseAppraise> wrapper = new QueryWrapper<>();
    String type =
        groupByYear
            ? ArchConst.FIELD_YEAR
            : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                ? "work_type"
                : "offer_user_id";
    wrapper
        .select(type + ",score,count(appraise_user_id) as id")
        .lambda()
        .eq(UseAppraise::getFondsId, SecurityUtil.getFondsId())
        .between(
            ObjectUtil.isNotNull(req.getStartYear()),
            UseAppraise::getYear,
            req.getStartYear(),
            req.getEndYear())
        .groupBy(
            groupByYear
                ? UseAppraise::getYear
                : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                    ? UseAppraise::getWorkType
                    : UseAppraise::getOfferUserId)
        .isNotNull(UseAppraise::getScore)
        .isNotNull(
            groupByYear
                ? UseAppraise::getYear
                : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                    ? UseAppraise::getWorkType
                    : UseAppraise::getOfferUserId)
        .groupBy(UseAppraise::getScore);
    return wrapper;
  }

  public List<UseAppraiseScoreListVO> buildScoreList(Integer statType, List<UseAppraise> list) {
    boolean groupByYear =
        ObjectUtil.isNull(statType)
            || ObjectUtil.equals(StatisticsTypeEnum.YEAR.getCode(), statType);
    Map<Number, List<UseAppraise>> group =
        list.stream()
            .collect(
                Collectors.groupingBy(
                    groupByYear
                        ? UseAppraise::getYear
                        : ObjectUtil.equals(statType, StatisticsTypeEnum.WORK_TYPE.getCode())
                            ? UseAppraise::getWorkType
                            : UseAppraise::getOfferUserId,
                    Collectors.toList()));
    String title =
        groupByYear
            ? ArchConst.FIELD_YEAR
            : ObjectUtil.equals(statType, StatisticsTypeEnum.WORK_TYPE.getCode())
                ? "workTypeName"
                : "offerName";
    List<Map<String, Object>> resList =
        group.entrySet().stream()
            .map(
                stat -> {
                  Map<?, UseAppraise> map =
                      stat.getValue().stream()
                          .collect(Collectors.toMap(UseAppraise::getScore, Function.identity()));
                  Map<String, Object> vo = Maps.newHashMap();
                  if (ObjectUtil.equals(statType, StatisticsTypeEnum.YEAR.getCode())) {
                    vo.put(ArchConst.FIELD_YEAR, stat.getKey());
                  } else if (ObjectUtil.equals(statType, StatisticsTypeEnum.WORK_TYPE.getCode())) {
                    // 按类型
                    UseOrderTypeEnum typeEnum = UseOrderTypeEnum.convert(stat.getKey().intValue());
                    vo.put(title, typeEnum.getDesc());
                  } else {
                    // 按处理人
                    SysUser user =
                        Optional.ofNullable(userSvc.getById(stat.getKey().longValue()))
                            .orElse(new SysUser());
                    vo.put(title, user.getRealname());
                  }

                  IntStream.range(1, 6)
                      .forEach(
                          score -> {
                            UseAppraise statUsing =
                                Optional.ofNullable(map.get(score)).orElse(new UseAppraise());

                            vo.put("s" + score, Optional.ofNullable(statUsing.getId()).orElse(0L));
                          });
                  OptionalDouble average =
                      stat.getValue().stream()
                          .mapToLong(
                              s ->
                                  Optional.ofNullable(s.getScore()).orElse(0)
                                      * Optional.ofNullable(s.getId()).orElse(0L))
                          .average();
                  double score = average.isPresent() ? average.getAsDouble() : 0;
                  vo.put("s0", score);
                  return vo;
                })
            .collect(Collectors.toList());
    // 合计
    Map<String, Object> totalMap = Maps.newHashMap();
    totalMap.put(title, "合计");
    IntStream.range(1, 6)
        .forEach(
            score -> {
              long sum =
                  list.stream()
                      .filter(s -> ObjectUtil.equals(s.getScore(), score))
                      .mapToLong(UseAppraise::getId)
                      .sum();
              totalMap.put("s" + score, sum);
            });
    OptionalDouble s0 = resList.stream().mapToDouble(s -> (double) s.get("s0")).average();
    double score = s0.isPresent() ? s0.getAsDouble() : 0;
    totalMap.put("s0", score);
    resList.add(totalMap);
    return BeanUtil.copyToList(resList, UseAppraiseScoreListVO.class);
  }

  @Override
  public Page<Map<String, Object>> selectScoreWithWorkTypePage(
      Page reqPage, UseAppraiseQueryDTO req) {
    boolean groupByYear =
        ObjectUtil.isNull(req.getStatType())
            || ObjectUtil.equals(StatisticsTypeEnum.YEAR.getCode(), req.getStatType());
    QueryWrapper<UseAppraise> wrapper = new QueryWrapper<>();
    String type =
        groupByYear
            ? ArchConst.FIELD_YEAR
            : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                ? "work_type"
                : "offer_user_id";
    wrapper
        .select(type + ",work_type,score,count(appraise_user_id) as id")
        .lambda()
        .eq(UseAppraise::getFondsId, SecurityUtil.getFondsId())
        .between(
            ObjectUtil.isNotNull(req.getStartYear()),
            UseAppraise::getYear,
            req.getStartYear(),
            req.getEndYear())
        .groupBy(
            groupByYear
                ? UseAppraise::getYear
                : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                    ? UseAppraise::getWorkType
                    : UseAppraise::getOfferUserId)
        .groupBy(UseAppraise::getWorkType)
        .isNotNull(UseAppraise::getScore)
        .isNotNull(UseAppraise::getWorkType)
        .isNotNull(
            groupByYear
                ? UseAppraise::getYear
                : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                    ? UseAppraise::getWorkType
                    : UseAppraise::getOfferUserId)
        .groupBy(UseAppraise::getScore);
    Page<UseAppraise> page = useAppraiseMapper.selectPage(reqPage, wrapper);
    Page<Map<String, Object>> voPage = new Page<>(reqPage.getCurrent(), reqPage.getSize());
    voPage.setTotal(page.getTotal());
    if (ObjectUtil.isEmpty(page.getRecords())) {
      return voPage;
    }

    Map<Number, List<UseAppraise>> group =
        page.getRecords().stream()
            .collect(
                Collectors.groupingBy(
                    groupByYear
                        ? UseAppraise::getYear
                        : ObjectUtil.equals(
                                req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                            ? UseAppraise::getWorkType
                            : UseAppraise::getOfferUserId,
                    Collectors.toList()));
    String title =
        groupByYear
            ? ArchConst.FIELD_YEAR
            : ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())
                ? "workTypeName"
                : "offerName";
    List<Map<String, Object>> resList =
        group.entrySet().stream()
            .map(
                stat -> {
                  Map<?, UseAppraise> map =
                      stat.getValue().stream()
                          .collect(Collectors.toMap(UseAppraise::getWorkType, Function.identity()));
                  Map<String, Object> vo = Maps.newHashMap();
                  if (ObjectUtil.equals(req.getStatType(), StatisticsTypeEnum.YEAR.getCode())) {
                    vo.put(ArchConst.FIELD_YEAR, stat.getKey());
                  } else if (ObjectUtil.equals(
                      req.getStatType(), StatisticsTypeEnum.WORK_TYPE.getCode())) {
                    // 按类型
                    UseOrderTypeEnum typeEnum = UseOrderTypeEnum.convert(stat.getKey().intValue());
                    vo.put(title, typeEnum.getDesc());
                  } else {
                    // 按处理人
                    SysUser user =
                        Optional.ofNullable(userSvc.getById(stat.getKey().longValue()))
                            .orElse(new SysUser());
                    vo.put(title, user.getRealname());
                  }

                  Arrays.stream(UseOrderTypeEnum.values())
                      .forEach(
                          workType -> {
                            UseAppraise statUsing =
                                Optional.ofNullable(map.get(workType.getCode()))
                                    .orElse(new UseAppraise());

                            vo.put(
                                "s" + workType.getCode(),
                                Optional.ofNullable(statUsing.getId()).orElse(0L));
                          });

                  OptionalDouble average =
                      stat.getValue().stream()
                          .mapToLong(
                              s ->
                                  Optional.ofNullable(s.getScore()).orElse(0)
                                      * Optional.ofNullable(s.getId()).orElse(0L))
                          .average();
                  double score = average.isPresent() ? average.getAsDouble() : 0;
                  vo.put("s0", score);
                  return vo;
                })
            .collect(Collectors.toList());
    voPage.setRecords(resList);
    return voPage;
  }

  @Override
  public Page<UseAppraiseEffectListVO> selectEffectPage(Page reqPage, UseAppraiseQueryDTO req) {
    QueryWrapper<UseAppraise> wrapper = buildEffectQuery(req);

    Page<UseAppraise> page = useAppraiseMapper.selectPage(reqPage, wrapper);
    Page<UseAppraiseEffectListVO> voPage = new Page<>(reqPage.getCurrent(), reqPage.getSize());
    voPage.setTotal(page.getTotal());
    if (ObjectUtil.isEmpty(page.getRecords())) {
      return voPage;
    }
    List<UseAppraiseEffectListVO> list = buildEffectList(req.getStatType(), page.getRecords());
    voPage.setRecords(list);
    return voPage;
  }

  @Override
  public List<UseAppraiseEffectListVO> selectEffectList(UseAppraiseQueryDTO req) {
    QueryWrapper<UseAppraise> wrapper = buildEffectQuery(req);
    List<UseAppraise> list = list(wrapper);
    return buildEffectList(req.getStatType(), list);
  }

  public QueryWrapper<UseAppraise> buildEffectQuery(UseAppraiseQueryDTO req) {
    boolean groupByYear =
        ObjectUtil.isNull(req.getStatType())
            || ObjectUtil.equals(StatisticsTypeEnum.YEAR.getCode(), req.getStatType());
    QueryWrapper<UseAppraise> wrapper = new QueryWrapper<>();
    String type = groupByYear ? ArchConst.FIELD_YEAR : "offer_dept_id";
    wrapper
        .select(type + ",work_type,effect,count(appraise_user_id) as id")
        .lambda()
        .eq(UseAppraise::getFondsId, SecurityUtil.getFondsId())
        .between(
            ObjectUtil.isNotNull(req.getStartYear()),
            UseAppraise::getYear,
            req.getStartYear(),
            req.getEndYear())
        .groupBy(groupByYear ? UseAppraise::getYear : UseAppraise::getOfferDeptId)
        .groupBy(UseAppraise::getWorkType)
        .isNotNull(UseAppraise::getEffect)
        .groupBy(UseAppraise::getEffect);
    return wrapper;
  }

  public List<UseAppraiseEffectListVO> buildEffectList(Integer statType, List<UseAppraise> list) {
    boolean groupByYear =
        ObjectUtil.isNull(statType)
            || ObjectUtil.equals(StatisticsTypeEnum.YEAR.getCode(), statType);
    Map<Long, String> archTypeMap = Maps.newHashMap();
    if (!groupByYear) {
      List<Long> deptId =
          list.stream().map(UseAppraise::getOfferDeptId).collect(Collectors.toList());
      List<SysDept> sysDepts = deptSvc.listByIds(deptId);
      archTypeMap = sysDepts.stream().collect(Collectors.toMap(SysDept::getId, SysDept::getName));
    }

    Map<Number, List<UseAppraise>> group =
        list.stream()
            .filter(
                s ->
                    groupByYear
                        ? ObjectUtil.isNotNull(s.getYear())
                        : ObjectUtil.isNotNull(s.getOfferDeptId()))
            .collect(
                Collectors.groupingBy(
                    groupByYear ? UseAppraise::getYear : UseAppraise::getOfferDeptId,
                    Collectors.toList()));
    String title = groupByYear ? ArchConst.FIELD_YEAR : "deptName";
    Map<Long, String> finalArchTypeMap = archTypeMap;
    List<Map<String, Object>> resList =
        group.entrySet().stream()
            .map(
                stat -> {
                  Map<Integer, List<UseAppraise>> map =
                      stat.getValue().stream()
                          .collect(
                              Collectors.groupingBy(UseAppraise::getWorkType, Collectors.toList()));
                  Map<String, Object> vo = Maps.newHashMap();
                  if (groupByYear) {
                    vo.put(title, stat.getKey().intValue());
                  } else {
                    long deptId = stat.getKey().longValue();
                    vo.put(title, finalArchTypeMap.get(deptId));
                  }
                  Arrays.stream(UseOrderTypeEnum.values())
                      .forEach(
                          orderType -> {
                            List<UseAppraise> effectList =
                                Optional.ofNullable(map.get(orderType.getCode()))
                                    .orElse(Lists.newArrayList());
                            Map<Integer, UseAppraise> appraiseMap =
                                effectList.stream()
                                    .collect(
                                        Collectors.toMap(
                                            UseAppraise::getEffect, Function.identity()));

                            IntStream.range(0, 2)
                                .forEach(
                                    score -> {
                                      UseAppraise statUsing =
                                          Optional.ofNullable(appraiseMap.get(score))
                                              .orElse(new UseAppraise());

                                      vo.put(
                                          "userNum" + orderType.getCode() + score,
                                          Optional.ofNullable(statUsing.getId()).orElse(0L));
                                    });
                          });

                  Long userNumSum0 =
                      stat.getValue().stream()
                          .filter(a -> ObjectUtil.equals(a.getEffect(), 0))
                          .mapToLong(UseAppraise::getId)
                          .sum();
                  Long userNumSum1 =
                      stat.getValue().stream()
                          .filter(a -> ObjectUtil.equals(a.getEffect(), 1))
                          .mapToLong(UseAppraise::getId)
                          .sum();
                  vo.put("totalUserNum0", userNumSum0);
                  vo.put("totalUserNum1", userNumSum1);
                  return vo;
                })
            .collect(Collectors.toList());

    // 合计
    Map<String, Object> totalMap = Maps.newHashMap();
    totalMap.put(title, "合计");
    Arrays.stream(UseOrderTypeEnum.values())
        .forEach(
            typeEnum -> {
              IntStream.range(0, 2)
                  .forEach(
                      effect -> {
                        long sum =
                            list.stream()
                                .filter(s -> ObjectUtil.equals(s.getWorkType(), typeEnum.getCode()))
                                .filter(s -> ObjectUtil.equals(s.getEffect(), effect))
                                .mapToLong(UseAppraise::getId)
                                .sum();

                        totalMap.put("userNum" + typeEnum.getCode() + effect, sum);
                      });
            });

    totalMap.put(
        "totalUserNum0", resList.stream().mapToLong(map -> (long) map.get("totalUserNum0")).sum());
    totalMap.put(
        "totalUserNum1", resList.stream().mapToLong(map -> (long) map.get("totalUserNum1")).sum());
    resList.add(totalMap);
    return BeanUtil.copyToList(resList, UseAppraiseEffectListVO.class);
  }
}
